/**
 * 
 */
package truerefactor.codesmell;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

import truerefactor.graph.ClassNode;
import truerefactor.graph.CodeGraph;
import truerefactor.graph.CodeNode;
import truerefactor.graph.MethodNode;
import truerefactor.graph.StatementNode;
import truerefactor.graph.StatementType;
import truerefactor.metrics.CS;
import truerefactor.metrics.WMC;
import truerefactor.refactor.Refactoring;
import truerefactor.refactor.RefactoringFactory;

/**
 * @author Isaac
 */
public class LongMethod extends CodeSmell {

    /**
     * 
     */
    public LongMethod()
    {
        this.shortName = "LNGM";
        this.name = "Long Method";
        this.description = "";
    }

    /*
     * (non-Javadoc)
     * @see
     * truerefactor.codesmell.CodeSmell#measure(truerefactor.graph.CodeGraph,
     * boolean, java.util.List)
     */
    @Override
    protected double measure(CodeGraph graph, boolean initial, List<List<Refactoring>> sequences)
    {
        // if classWMC > medianWMC && NOM < medianNOM
        // if LOC >= medianLOC && (WMC / NOM) >= 2.0

        double smellFound = 0;
        int numMethods = 0;
        CS cs = new CS();
        WMC wmc = new WMC();

        double csMedian = cs.measure(graph);
        double wmcMedian = wmc.measure(graph);

        List<ClassNode> classes = graph.getClasses();
        List<MethodNode> longMethods = new ArrayList<MethodNode>();

        for (ClassNode cNode : classes)
        {
            numMethods = cNode.getMethods().size();

            if (numMethods > 0)
            {
                if ((CS.measure(cNode) < csMedian) && ((WMC.measure(cNode) / numMethods) >= 2))
                {
                    if (initial)
                    {
                        MethodNode longMethod = findLongMethod(cNode);
                        if (longMethod != null)
                            longMethods.add(longMethod);
                    }
                    smellFound++;
                }
                else if (WMC.measure(cNode) > wmcMedian && CS.measure(cNode) < csMedian)
                {
                    if (initial)
                    {
                        MethodNode longMethod = findLongMethod(cNode);
                        if (longMethod != null)
                            longMethods.add(longMethod);
                    }
                    smellFound++;
                }
            }
        }

        if (initial && !longMethods.isEmpty())
        {
            for (MethodNode mNode : longMethods)
            {
                sequences.add(decide(mNode));
            }
        }

        return smellFound;
    }

    private MethodNode findLongMethod(ClassNode cNode)
    {
        double largeCC = 0;
        MethodNode longMethod = null;
        for (CodeNode node : cNode.getMethods())
        {
            if (node instanceof MethodNode)
            {
                double value = WMC.calcComplexity((MethodNode) node);
                if (value > largeCC)
                {
                    largeCC = value;
                    longMethod = (MethodNode) node;
                }
            }
        }

        return longMethod;
    }

    private List<Refactoring> decide(MethodNode method)
    {
        List<Refactoring> retVal = new ArrayList<Refactoring>();

        if (method.getParameters().size() >= 4)
        {
            CodeNode operands[] = { method };
            retVal.add(RefactoringFactory.getInstance().createIntroduceParamObject(operands));
            // retVal.add(RefactoringFactory.getInstance().createPreserveWholeObject(operands));
        }
        else
        {
            HashMap<StatementNode, Integer> tempVars = new HashMap<StatementNode, Integer>();
            List<CodeNode> stmts = method.getStatements();
            int count = 0;
            for (CodeNode stmt : stmts)
            {
                if (stmt instanceof StatementNode)
                {
                    String type = stmt.getIdentifier();
                    if (type.equals(StatementType.EXPRESSION.getIdentifier()))
                    {
                        tempVars.put((StatementNode) stmt, count);
                        count++;
                    }
                }
            }

            if (tempVars.size() >= 4)
            {
                for (StatementNode stmt : tempVars.keySet())
                {
                    CodeNode operands[] = { stmt };
                    retVal.add(RefactoringFactory.getInstance().createReplaceTempWithQuery(operands));
                }
            }
        }

        CodeNode operands[] = { method };
        retVal.add(RefactoringFactory.getInstance().createExtractMethod(operands));

        return retVal;
    }
}
